package com.slipper.codeless.trigger;

import com.slipper.groovy.service.GroovyService;
import com.slipper.mybatis.Trigger;
import com.slipper.system.codeless.bo.DataModelBO;
import com.slipper.util.DatabaseUtil;
import com.slipper.util.Tool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class CodelessTrigger implements Trigger {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final ConcurrentHashMap<String, String> typeTableMap = new ConcurrentHashMap<>();

    public void updateTrigger(DataModelBO dataModel) {
        registerGroovyTrigger(BEFORE_INSERT, dataModel.getCode(), dataModel.getBeforeInsertId());
        registerGroovyTrigger(BEFORE_UPDATE, dataModel.getCode(), dataModel.getBeforeUpdateId());
        registerGroovyTrigger(BEFORE_DELETE, dataModel.getCode(), dataModel.getBeforeDeleteId());
        registerGroovyTrigger(AFTER_INSERT, dataModel.getCode(), dataModel.getAfterInsertId());
        registerGroovyTrigger(AFTER_UPDATE, dataModel.getCode(), dataModel.getAfterUpdateId());
        registerGroovyTrigger(AFTER_DELETE, dataModel.getCode(), dataModel.getAfterDeleteId());
    }

    private void registerGroovyTrigger(String type, String tableName, String scriptId) {
        if (Tool.isBlank(scriptId)) {
            unregisterGroovyTrigger(type, tableName);
        } else {
            typeTableMap.put(createKey(type, tableName), scriptId);
        }
    }

    private void unregisterGroovyTrigger(String type, String tableName) {
        typeTableMap.remove(createKey(type, tableName));
    }

    @Override
    @SuppressWarnings("unchecked")
    public void trigger(String type, Map<String, Object> paramMap) {
        String tableName = parseTableName(paramMap);
        if (Tool.isBlank(tableName)) return;

        String scriptId = typeTableMap.get(createKey(type, tableName));
        if (Tool.isBlank(scriptId)) return;

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("type", type);

        if (BEFORE_DELETE.equalsIgnoreCase(type) || AFTER_DELETE.equalsIgnoreCase(type)) {
            dataMap.put("ids", getIds(paramMap));
        } else {
            dataMap.put("data", getData(paramMap));
        }

        logger.info("数据模型【{}】，触发【{}】事件，脚本编码【{}】...", tableName, type, scriptId);
        Object result = GroovyService.runById(scriptId, dataMap);

        if ((BEFORE_INSERT.equalsIgnoreCase(type) || BEFORE_UPDATE.equalsIgnoreCase(type)) && result instanceof Map) {
            addParameter(type, (Map<String, Object>) result, paramMap);
        }
    }

    private String createKey(String type, String tableName) {
        return type + "_" + tableName;
    }

    private List<Object> getIds(Map<?, ?> paramMap) {
        List<Object> idList = new ArrayList<>();
        paramMap.forEach((key, value) -> {
            if (key.toString().startsWith("pkId_")) {
                idList.add(value);
            }
        });
        return idList;
    }

    private Map<String, Object> getData(Map<?, ?> paramMap) {
        Map<String, Object> resultMap = new HashMap<>();
        paramMap.forEach((key, value) -> {
            if (!"sql".equalsIgnoreCase(key.toString())) {
                resultMap.put(key.toString(), value);
            }
        });
        return resultMap;
    }

    private void addParameter(String type, Map<String, Object> addMap, Map<String, Object> paramMap) {
        if (addMap.isEmpty()) return;

        String sql = (String) paramMap.get("sql");
        if (BEFORE_INSERT.equalsIgnoreCase(type)) {
            // insert into table() values ();
            String prefix = sql.substring(0, sql.indexOf('(') + 1);
            StringBuilder fields = new StringBuilder(sql.substring(sql.indexOf('(') + 1, sql.indexOf(")")));
            String middle = sql.substring(sql.indexOf(')'), sql.lastIndexOf("(") + 1);
            StringBuilder values = new StringBuilder(sql.substring(sql.lastIndexOf('(') + 1, sql.lastIndexOf(")")));
            String suffix = sql.substring(sql.lastIndexOf(")"));

            for (Map.Entry<String, Object> entry : addMap.entrySet()) {
                if (paramMap.containsKey(entry.getKey())) return;

                String paramKey = "gy_" + entry.getKey();
                fields.append(", ").append(DatabaseUtil.wrapSymbol(entry.getKey()));
                values.append("#{").append(paramKey).append("}");
                paramMap.put(paramKey, entry.getValue());
            }

            paramMap.put("sql", prefix + fields + middle + values + suffix);
        } else if (BEFORE_UPDATE.equalsIgnoreCase(type)) {
            // update table set where id = id;
            sql = sql.replaceAll("\n", " ");
            StringBuilder prefix = new StringBuilder(sql.substring(0, sql.indexOf(" where ")));
            String where = sql.substring(sql.lastIndexOf(" where "));

            for (Map.Entry<String, Object> entry : addMap.entrySet()) {
                if (paramMap.containsKey(entry.getKey())) return;

                String paramKey = "gy_" + entry.getKey();
                prefix.append(", ").append(DatabaseUtil.wrapSymbol(entry.getKey())).append("=");
                prefix.append("#{").append(paramKey).append("}");
                paramMap.put(paramKey, entry.getValue());
            }

            paramMap.put("sql", prefix + where);
        }
    }

    private String parseTableName(Map<?, ?> paramMap) {
        String sql = (String) paramMap.get("sql");
        if (Tool.isBlank(sql)) return null;

        String[] strs = sql.trim().toLowerCase()
                .replaceAll("[\n\"`']", " ")
                .replaceAll(" {2,}", " ")
                .split("[ (]");
        if (strs[0].startsWith("update")) {
            return strs[1];
        } else if (strs[0].startsWith("insert") || strs[0].startsWith("delete")) {
            return strs[2];
        } else {
            return null;
        }
    }
}
